/*
 *      stone.cpp
 *      
 *      Copyright 2008 Antti Leivo <skylark@skylark-desktop>
 *      
 *      This program is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation; either version 2 of the License, or
 *      (at your option) any later version.
 *      
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 *      
 *      You should have received a copy of the GNU General Public License
 *      along with this program; if not, write to the Free Software
 *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 *      MA 02110-1301, USA.
 */


#include "gravsimGUI.h"
#include <iostream>
#include <string>

#define DEBUG_LEVEL 0
#define GUI_RAD2DEG(x) (57.295779513*(x))

GravsimGUI::GravsimGUI(World *_w)
	: w(_w), status(sysUnready), console("Debug console window", 80, 25), sound_output(0),
	window(0), gc(0), resources(0), spr_ball(0), spr_box(0)
{
	console.redirect_stdio();

    try
    {
        /* clanlib objects */
        sound_output = new CL_SoundOutput(44100);
        window = new CL_DisplayWindow("2D-Space Gravity Simulation", 800, 600, false);
 		gc = window->get_gc();
 		gc->clear(CL_Color::brown);
 		
 		/* keyboard/mouse input */
 		inputbuffer.add_device(window->get_ic()->get_keyboard());
 		inputbuffer.add_device(window->get_ic()->get_mouse());

        /* sprite resources */
		resources = new CL_ResourceManager("resources.xml");
		if (resources == NULL)
		{
			std::cout << "GravsimGUI::Cannot load resources." << std::endl;
		}
		else
		{
			std::cout << "GravsimGUI::start of resource list-----------------------\n";
			std::vector<std::string> res = resources->get_all_resources();
			for (std::vector<std::string>::iterator i=res.begin(); i!=res.end(); i++)
				std::cout << *i << std::endl;
			std::cout << "Gravsim::end of resource list ---------------------------" << std::endl;
		}
		spr_ball = new CL_Sprite("Objects/stoneball", resources);
		spr_ball->update();
		spr_box = new CL_Sprite("Objects/stonebox", resources);
		spr_box->update();
		
		#if (DEBUG_LEVEL >= 1)
			CL_Origin origin;
			int x,y;
			spr_ball->get_rotation_hotspot(origin, x, y);
			std::cout << "GRAVSIMGUI::sprite rot hotspot: "
			          << origin << ",x=" << x << ",y=" << y << std::endl;
			spr_ball->get_alignment(origin, x, y);
			std::cout << "GRAVSIMGUI::sprite alignment: "
			          << origin << ",x=" << x << ",y=" << y << std::endl;
	    #endif

		status = sysRunnable;
    }
    catch (CL_Error err)
    {
    	status = sysFailure;
        std::cout << "GravsimGUI::Exeception caught: " << err.message.c_str() << std::endl;
        console.wait_for_key();
    }
}

GravsimGUI::~GravsimGUI()
{
	delete sound_output;
	delete window;
}

 
void GravsimGUI::run()
{
	unsigned long time_stamp, time_stamp_prev=0;
	/* stop if not initialized/has errors */
	if (status != sysRunnable)
		return;
    status = sysRunning;
    /* loop */
 	while (status == sysRunning)
    {
        /* loop start */

        /* process user commands */
        processInputEvents();

        /* update world */
        #if (DEBUG_LEVEL >= 1)
	      std::cout << "WORLDGUI::update world" << std::endl;
	    #endif
		if (gcv.sim_speed_scaler != 0)
			w->step();

        /* draw screen */
		draw();

        /* wait if necessary to maintain constant cycle time */

		overrun=false;
   		time_stamp = CL_System::get_time();
        if (gcv.sim_speed_scaler != 0)
        {
           	if (time_stamp - time_stamp_prev > 1000*gcv.timestep/gcv.sim_speed_scaler)
           	{
   		        #if (DEBUG_LEVEL >= 1)
	    			std::cout << "WORLDGUI::OVERRUN! "
	    			          << " ms. T=" << time_stamp << ", Tpr="
	    			          << time_stamp_prev << std::endl;
			    #endif
			    CL_System::keep_alive();
           		overrun = true;
           	}
           	else {
   		        #if (DEBUG_LEVEL >= 1)
	    			std::cout << "WORLDGUI::sleep "
	    			          << 1000*gcv.timestep/gcv.sim_speed_scaler - time_stamp + time_stamp_prev
	    			          << " ms. T=" << time_stamp << ", Tpr="
	    			          << time_stamp_prev << std::endl;
			    #endif
                /* sleep or keep_alive */
           		CL_System::keep_alive(1000*gcv.timestep/gcv.sim_speed_scaler - time_stamp + time_stamp_prev);
 			}
		}
   		time_stamp_prev = CL_System::get_time();
	} // sim-loop
} // run-function


void GravsimGUI::processInputEvents()
{
	/* listen keys/mouse */
	CL_InputEvent event;
	#if (DEBUG_LEVEL >= 2)
	    std::cout << "GravsimGUI::processInput: keys " << inputbuffer.keys_left() << std::endl;
	#endif
	while (inputbuffer.keys_left() > 0)
	{
		event = inputbuffer.pop_key();
		#if (DEBUG_LEVEL >= 1)
			std::cout << "GravsimGUI::processInput: got event [" << event.str << "]" << std::endl;
		#endif
		if (event.type == CL_InputEvent::pressed)
		{
			/* some key is pressed */
			switch(event.id) {
			case CL_KEY_LEFT:
				gcv.scroll_left = true;
				break;
			case CL_KEY_UP:
				gcv.scroll_up = true;
				break;
			case CL_KEY_RIGHT:
				gcv.scroll_right = true;
				break;
			case CL_KEY_DOWN:
				gcv.scroll_down = true;
				break;
			case CL_KEY_NUMPAD_ADD:
				gcv.zoom_in = true;
				break;
			case CL_KEY_NUMPAD_SUBTRACT:
				gcv.zoom_out = true;
				break;
			case CL_KEY_ESCAPE:
				status = sysShuttingDown;
				break;
			default:
				break;
			}
		} // button pressed
		else if (event.type == CL_InputEvent::released)
		{
			/* some key is released */
			switch(event.id) {
			case CL_KEY_LEFT:
				gcv.scroll_left = false;
				break;
			case CL_KEY_UP:
				gcv.scroll_up = false;
				break;
			case CL_KEY_RIGHT:
				gcv.scroll_right = false;
				break;
			case CL_KEY_DOWN:
				gcv.scroll_down = false;
				break;
			case CL_KEY_NUMPAD_ADD:
				gcv.zoom_in = false;
				break;
			case CL_KEY_NUMPAD_SUBTRACT:
				gcv.zoom_out = false;
				break;
			default:
				break;
			} // key switch
		} // button released
	}
	
	/* apply continuous key/mouse commands */
	if (gcv.zoom_in)
	{
		gcv.focus_dist *= (1.0-GRAVSIMGUI_DEFAULT_ZOOM_SPEED*gcv.timestep);
	}
	if (gcv.zoom_out)
	{
		gcv.focus_dist *= (1.0+GRAVSIMGUI_DEFAULT_ZOOM_SPEED*gcv.timestep);;
	}
	if (gcv.scroll_left)
	{
		gcv.focus_x -= GRAVSIMGUI_DEFAULT_SCROLL_SPEED*gcv.timestep*gcv.focus_dist;
	}
	if (gcv.scroll_right)
	{
		gcv.focus_x += GRAVSIMGUI_DEFAULT_SCROLL_SPEED*gcv.timestep*gcv.focus_dist;
	}
	if (gcv.scroll_up)
	{
		gcv.focus_y -= GRAVSIMGUI_DEFAULT_SCROLL_SPEED*gcv.timestep*gcv.focus_dist;
	}
	if (gcv.scroll_down)
	{
		gcv.focus_y += GRAVSIMGUI_DEFAULT_SCROLL_SPEED*gcv.timestep*gcv.focus_dist;
	}
	
}


void GravsimGUI::draw()
{
 #if (DEBUG_LEVEL >= 1)
   try
    {
#endif 
	gc->clear();
	for(std::vector<Stone*>::iterator i = w->stones.begin(); i != w->stones.end(); i++)
	{
		drawStone(*i);
		// debugging sleep  CL_System::keep_alive(1000);
	}
	CL_Display::flip();

 #if (DEBUG_LEVEL >= 1)
    }
    catch (CL_Error err)
    {
        std::cout << "GravsimGUI::Exeception caught: " << err.message.c_str() << std::endl;
        status = sysFailure;
        //console.wait_for_key();
    }
#endif 

}

void GravsimGUI::drawStone(Stone *s)
{
        #if (DEBUG_LEVEL >= 2)
        	std::cout << "GRAVSIMGUI::DrawStone: r=" << s->radius
        	<< ", p=(" << s->body->p.x
        	<< ", "    << s->body->p.y << ")"
        	<< ", spr_width=" << spr_ball->get_width()
        	<< ", dist=" << gcv.focus_dist << std::endl;
        #endif

		if (s->form.type == stBall)
		{
			/* draw ball */
			float scale = s->form.width/(spr_ball->get_width()*gcv.focus_dist);
			spr_ball->set_angle(GUI_RAD2DEG(s->body->a));
			spr_ball->set_scale(scale, scale);
       	    spr_ball->draw(visual_x(s->body->p.x), visual_y(s->body->p.y), gc);
       	}
       	else {
       		/* draw crate */
			float scalex = s->form.width/(spr_box->get_width()*gcv.focus_dist);
			float scaley = s->form.height/(spr_box->get_height()*gcv.focus_dist);
			spr_box->set_angle(GUI_RAD2DEG(s->body->a));
			spr_box->set_scale(scalex, scaley);
       	    spr_box->draw(visual_x(s->body->p.x), visual_y(s->body->p.y), gc);
       	}
}


float GravsimGUI::world_x(int visual_x)
{
	return gcv.focus_x + gcv.focus_dist * (visual_x - gc->get_width()/2);
}

float GravsimGUI::world_y(int visual_y)
{
	return gcv.focus_y + gcv.focus_dist * (visual_y - gc->get_height()/2);
}

int GravsimGUI::visual_x(float world_x)
{
	#if (DEBUG_LEVEL >= 2)
		std::cout << "GRAVSIMGUI::VISUALX: vx(" << world_x << ") = "
		<< (int)((world_x-gcv.focus_x)/gcv.focus_dist + gc->get_width()/2)
		<< std::endl;
	#endif
	return (int)((world_x-gcv.focus_x)/gcv.focus_dist + gc->get_width()/2);
}

int GravsimGUI::visual_y(float world_y)
{
	#if (DEBUG_LEVEL >= 2)
		std::cout << "GRAVSIMGUI::VISUALX: vy(" << world_y << ") = "
		<< (int)((world_y-gcv.focus_y)/gcv.focus_dist + gc->get_height()/2)
		<< std::endl;
	#endif

	return (int)((world_y-gcv.focus_y)/gcv.focus_dist + gc->get_height()/2);
}
 

/* ---------------------- GUIControlVars class ------------------- */

GUIControlVars::GUIControlVars(void)
	: focus_x(0), focus_y(0), focus_dist(GRAVSIMGUI_DEFAULT_VIEW_DIST),
	sim_speed_scaler(GRAVSIMGUI_DEFAULT_SPEED_SCALER),
	timestep(GRAVSIMGUI_DEFAULT_TIMESTEP),
	zoom_in(false), zoom_out(false), scroll_up(false),
	scroll_down(false), scroll_left(false), scroll_right(false)
{
}
